// 版权归Go作者所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// go:构建amd64 | arm64 | mips64 | mips64le | ppc64 | | ppc64le | riscv64 | s390x 

package runtime

import "unsafe"

const (
	// 基树中的级别数。
	summaryLevels = 5

	// 用于测试的常数。
	pageAlloc32Bit = 0
	pageAlloc64Bit = 1

	// 表示所有索引到
	// 块映射的L1中所需的位数。
	// 
	// 请参阅（*pageAlloc）。更多细节，请点击区块。更新文件
	// 这个数字应该会改变。
	pallocChunksL1Bits = 13
)

// levelBits是超级摘要
// 结构中给定级别的基数位数。
// 
// levelBits的所有条目之和应等于heapdRbits。
var levelBits = [summaryLevels]uint{
	summaryL0Bits,
	summaryLevelBits,
	summaryLevelBits,
	summaryLevelBits,
	summaryLevelBits,
}

// levelShift是在超级摘要结构中，为获取给定级别的基数而移位的位数。
// 
// 使用levelShift，可以计算与
// 指针p相关的l级摘要的索引：
// p>>levelShift[l]
var levelShift = [summaryLevels]uint{
	heapAddrBits - summaryL0Bits,
	heapAddrBits - summaryL0Bits - 1*summaryLevelBits,
	heapAddrBits - summaryL0Bits - 2*summaryLevelBits,
	heapAddrBits - summaryL0Bits - 3*summaryLevelBits,
	heapAddrBits - summaryL0Bits - 4*summaryLevelBits,
}

// levelLogPages是log2地址空间中运行时页面的最大数量
// 给定级别的摘要代表。
// 
// 叶级别始终精确表示1个区块的页面的log2。
var levelLogPages = [summaryLevels]uint{
	logPallocChunkPages + 4*summaryLevelBits,
	logPallocChunkPages + 3*summaryLevelBits,
	logPallocChunkPages + 2*summaryLevelBits,
	logPallocChunkPages + 1*summaryLevelBits,
	logPallocChunkPages,
}

// sysInit对pageAlloc中的字段
// 执行与体系结构相关的初始化。pageAlloc应该是未初始化的，除了sysStat 
// 如果需要更新任何运行时统计信息。
func (p *pageAlloc) sysInit() {
	// 为每个级别保留内存。这将由setArenas以R/W的形式映射到
	// 中。
	for l, shift := range levelShift {
		entries := 1 << (heapAddrBits - shift)

		// 在地址空间的任何位置保留b字节的内存。
		b := alignUp(uintptr(entries)*pallocSumBytes, physPageSize)
		r := sysReserve(nil, b)
		if r == nil {
			throw("failed to reserve page summary memory")
		}

		// 把这个预订分成一部分。
		sl := notInHeapSlice{(*notInHeap)(r), 0, entries}
		p.summary[l] = *(*[]pallocSum)(unsafe.Pointer(&sl))
	}
}

// sysGrow在堆
// 页分配器的增长上执行与体系结构相关的操作，例如在新内存中映射
// 以获取摘要。它还更新了
// [.summary.
// 
// base是新添加堆内存的基础，limit是
// 超过新添加堆内存末尾的第一个地址。
// 两者必须与pallochunkbytes对齐。
// 
// 调用sysGrow.
func (p *pageAlloc) sysGrow(base, limit uintptr) {
	if base%pallocChunkBytes != 0 || limit%pallocChunkBytes != 0 {
		print("runtime: base = ", hex(base), ", limit = ", hex(limit), "\n")
		throw("sysGrow bounds not aligned to pallocChunkBytes")
	}

	// addrRangeToSummaryRang后，调用方必须更新p.start和p.ende将一个地址范围转换为一个摘要索引范围
	// ，必须映射该范围以支持摘要范围内的那些地址
	// 。
	addrRangeToSummaryRange := func(level int, r addrRange) (int, int) {
		sumIdxBase, sumIdxLimit := addrsToSummaryRange(level, r.base.addr(), r.limit.addr())
		return blockAlignSummaryRange(level, sumIdxBase, sumIdxLimit)
	}

	// summaryRange to sumaddrrange将任何
	// p.summary级别的索引范围转换为包含该索引范围的页面对齐地址。
	summaryRangeToSumAddrRange := func(level, sumIdxBase, sumIdxLimit int) addrRange {
		baseOffset := alignDown(uintptr(sumIdxBase)*pallocSumBytes, physPageSize)
		limitOffset := alignUp(uintptr(sumIdxLimit)*pallocSumBytes, physPageSize)
		base := unsafe.Pointer(&p.summary[level][0])
		return addrRange{
			offAddr{uintptr(add(base, baseOffset))},
			offAddr{uintptr(add(base, limitOffset))},
		}
	}

	// AddArrangeToSumAddressRange是一个方便的函数，它将地址范围r转换为给定摘要级别的地址范围
	// 存储r的摘要。
	addrRangeToSumAddrRange := func(level int, r addrRange) addrRange {
		sumIdxBase, sumIdxLimit := addrRangeToSummaryRange(level, r)
		return summaryRangeToSumAddrRange(level, sumIdxBase, sumIdxLimit)
	}

	// 查找严格大于基的第一个常用索引。
	// 
	// 因为这个函数永远不会被要求重新映射同一个内存
	// 两次，所以这个索引实际上是我们将插入
	// 这个新增长的索引，并且基永远不会重叠/包含在
	// 任何现有范围内。
	// 
	// 这将用于查看摘要数组中已映射的内存
	// 在这个新范围之前和之后。
	inUseIndex := p.inUse.findSucc(base)

	// 沿着根树走过去，根据需要绘制总结图。
	for l := range p.summary {
		// 找出这个新地址空间需要的摘要数组的哪一部分。
		needIdxBase, needIdxLimit := addrRangeToSummaryRange(l, makeAddrRange(base, limit))

		// 使用新的上限更新摘要切片。这确保了
		// 我们至少在上限上得到严格的边界检查。无论我们是否映射新内存，我们都必须这样做。
		if needIdxLimit > len(p.summary[l]) {
			p.summary[l] = p.summary[l][:needIdxLimit]
		}

		// 在l级的摘要数组中计算所需的地址范围。
		need := summaryRangeToSumAddrRange(l, needIdxBase, needIdxLimit)

		// 将需要删除到需要新映射的地址范围。由于页面对齐要求
		// 已经按照inUse描述的方式映射了部分内容
		// 以进行映射。prune的不变量是由这样一个事实保证的：这个
		// 函数永远不会被要求重新映射同一个内存两次。
		if inUseIndex > 0 {
			need = need.subtract(addrRangeToSumAddrRange(l, p.inUse.ranges[inUseIndex-1]))
		}
		if inUseIndex < len(p.inUse.ranges) {
			need = need.subtract(addrRangeToSumAddrRange(l, p.inUse.ranges[inUseIndex]))
		}
		// 有可能在我们修剪完上面的部分后，就没有什么新的东西需要绘制了。
		if need.size() == 0 {
			continue
		}

		// 映射并提交需求。
		sysMap(unsafe.Pointer(need.base.addr()), need.size(), p.sysStat)
		sysUsed(unsafe.Pointer(need.base.addr()), need.size())
	}
}
